#include "PEIncludes.h"

#include "PEAliceSoftImagePMS.h"

#include "PEFile.h"
#include "PEImage.h"

namespace pe
{

	int AliceImagePmsReadHeader(AliceImagePmsHeader* a_Target, PEStream* a_Stream)
	{
		PEStreamRead(a_Stream, a_Target, sizeof(AliceImagePmsHeader), 1);

		if (a_Target->header[0] != 'P' ||
			a_Target->header[1] != 'M'
		)
		{
			return -1;
		}

		return PE_OK;
	}

	int AliceImagePmsReadData(AliceImagePms* a_Target, AliceImagePmsHeader* a_Header, PEStream* a_Stream)
	{
		a_Target->header = new AliceImagePmsHeader;
		memcpy(a_Target->header, a_Header, sizeof(AliceImagePmsHeader));

		// seek to color table or data

		PEStreamSeek(a_Stream, a_Header->header_size, SEEK_SET);		

		// read color table

		if (a_Target->header->bpp == 8)
		{
			a_Target->colors = new AliceImagePmsColor[256];
			PEStreamRead(a_Stream, a_Target->colors, sizeof(AliceImagePmsColor), 256);
		}
		else
		{
			a_Target->colors = NULL;
		}

		// seek to data

		PEStreamSeek(a_Stream, a_Header->data_start, SEEK_SET);

		// get data

		PEStreamGetFileSize(a_Stream, &a_Target->data_size);

		a_Target->data = new byte[a_Target->data_size];
		PEStreamRead(a_Stream, a_Target->data, 1, a_Target->data_size);

		// set properties

		a_Target->width = a_Header->width;
		a_Target->height = a_Header->height;

		return PE_OK;
	}

	int PMSDecrypt16bit(PEImageRaw* a_Dst, AliceImagePms* a_Src)
	{
		unsigned int loc = 0;
		unsigned int index = 0;

		word* color_buffer = new word[a_Src->width * a_Src->height * 32];
		byte* src = a_Src->data;

		unsigned int pitch = a_Dst->width;

		PE_DEBUG("Data size: %i", a_Src->data_size);
		PE_DEBUG("Pitch: %i", pitch);

		for (unsigned int i = 0; i < a_Src->data_size; i++)
		{
			byte code = src[index];

			//PE_DEBUG("Code: 0x%x (Filled: %i / %i)", code, index, a_Src->data_size);

			if (index >= a_Src->data_size)
			{
				break;
			}
			index++;

			switch (code)
			{

			case 0xF9:
				{
					unsigned int length = (unsigned int)(src[index]) + 1;
					index++;

					byte index0 = src[index++];
					byte index1 = src[index++];

					word pc0 = ((index0 & 0xE0) << 8) + ((index0 & 0x18) << 6) + ((index0 & 0x07) << 2);
					word pc1 = ((index1 & 0xC0) << 5) + ((index1 & 0x3C) << 3) + ((index1 & 0x03)     );
					word pc2 = pc0 + pc1;

					color_buffer[loc] = pc2;

					for (unsigned int i = 1; i < length; i++) 
					{
						index1 = src[index++];

						pc1 = ((index1 & 0xC0) << 5) + ((index1 & 0x3C) << 3) + (index1 & 0x03);
						pc2 = pc0 | pc1;

						color_buffer[loc + i] = pc2;
					}

					loc += length;

					break;
				}

			case 0xFA:
				{
					color_buffer[loc] = color_buffer[loc - pitch + 1];
					loc++;

					break;

				}

			case 0xFB:
				{
					color_buffer[loc] = color_buffer[loc - pitch - 1];
					loc++;

					break;
				}

			case 0xFC:
				{
					unsigned int length = ((unsigned int)(src[index++]) + 2) * 2;
					
					byte c0 = src[index++];
					byte c1 = src[index++];
					word pc0 = (c0) | (c1 << 8);

					byte c2 = src[index++];
					byte c3 = src[index++];
					word pc1 = (c2) | (c3 << 8);

					for (unsigned int i = 0; i < length; i += 2) 
					{
						color_buffer[loc + i + 0] = pc0;
						color_buffer[loc + i + 1] = pc1;
					}

					loc += length;

					break;
				}

			case 0xFD:
				{
					unsigned int length = (unsigned int)(src[index++]) + 3;

					byte c0 = src[index++];
					byte c1 = src[index++];
					word pc0 = (c0) | (c1 << 8);

					for (unsigned int i = 0; i < length; i++) 
					{
						color_buffer[loc + i] = pc0;
					}

					loc += length;

					break;
				}

			case 0xFE:
				{
					unsigned int length = (unsigned int)(src[index++]) + 2;

					for (unsigned int i = 0; i < length; i++) 
					{
						color_buffer[loc + i] = color_buffer[loc - (pitch * 2) + i];
					}

					loc += length;

					break;
				}

			case 0xFF:
				{	
					unsigned int length = (unsigned int)(src[index++]) + 2;

					for (unsigned int i = 0; i < length; i++) 
					{
						color_buffer[loc + i] = color_buffer[loc - pitch + i];
					}

					loc += length;

					break;
				}

			default:
				{
					if (code <= 0xF7)
					{
						byte c1 = src[index++];
						color_buffer[loc] = code | (c1 << 8);

						loc++;
					}
					else
					{
						byte c0 = src[index++];
						byte c1 = src[index++];
						color_buffer[loc] = c0 | (c1 << 8);

						loc++;

						//PE_PRINT("ERROR! Unhandled code: 0x%x", code);
					}

					break;
				}
			}
		}
		
		// write to raw image

		for (unsigned int y = 0; y < a_Src->height; y++) 
		{
			byte* buffer = (byte*)&color_buffer[y * pitch];
			byte* dst = &a_Dst->data[y * a_Src->width * 4];

			for (unsigned int x = 0; x < a_Src->width; x++)
			{
				word col = (word)buffer[x * 2 + 0] + (word)(buffer[x * 2 + 1] * 256);
				dst[x * 4 + 2] = ((col >> 11) << 3) | 0x07;
				dst[x * 4 + 1] = (((col >> 5) & 0x3F) << 2) | 0x03;
				dst[x * 4 + 0] = ((col & 0x1F) << 3) | 0x07;
				dst[x * 4 + 3] = 255;
			}
		}

		delete color_buffer;

		return PE_OK;
	}

	unsigned int MakeColor(AliceImagePmsColor* a_Color)
	{
		return (unsigned int)(
			(a_Color->r <<  0) | 
			(a_Color->g <<  8) | 
			(a_Color->b << 16) | 
			(255        << 24)
		);
	}

	int PMSDecrypt8bit(PEImageRaw* a_Dst, AliceImagePms* a_Src)
	{
		unsigned int loc = 0;
		unsigned int index = 0;
		AliceImagePmsColor* col;

		unsigned int* pic = new unsigned int[a_Dst->width * a_Dst->height * 32];
		byte* src = a_Src->data;

		unsigned int pitch = a_Dst->width;

		for (unsigned int i = 0; i < a_Src->data_size; i++)
		{
			byte code = src[index];

			//PE_DEBUG("Code: 0x%x (Index: %i - Filled: %i / %i)", code, index, loc, a_Src->width * a_Src->height * 4);

			if (index >= a_Src->data_size)
			{
				break;
			}
			index++;

			switch (code)
			{

			case 0xFC:
				{
					unsigned int length = (unsigned int)(src[index++] + 3) * 2;

					AliceImagePmsColor* col0 = &a_Src->colors[src[index++]];
					AliceImagePmsColor* col1 = &a_Src->colors[src[index++]];

					for (unsigned int i = 0; i < length; i += 2) 
					{
						pic[loc + i + 0] = MakeColor(col0);
						pic[loc + i + 1] = MakeColor(col1);
					}

					loc += length;

					break;
				}
				
			case 0xFD:
				{
					unsigned int length = (unsigned int)(src[index++]) + 4;

					col = &a_Src->colors[src[index++]];

					for (unsigned int i = 0; i < length; i++) 
					{
						pic[loc + i] = MakeColor(col);
					}

					loc += length;
					
					break;
				}

			case 0xFE:
				{
					unsigned int length = (unsigned int)(src[index++]) + 3;

					for (unsigned int i = 0; i < length; i++) 
					{
						pic[loc + i] = pic[loc - (pitch * 2) + i];
					}

					loc += length;

					break;
				}

			case 0xFF:
				{			
					unsigned int length = (unsigned int)(src[index++]) + 3;

					for (unsigned int i = 0; i < length; i++) 
					{
						pic[loc + i] = pic[loc - pitch + i];
					}

					loc += length;

					break;
				}

			default:
				{
					if (code <= 0xF7)
					{
						col = &a_Src->colors[code];
						pic[loc++] = MakeColor(col);
					}
					else
					{
						col = &a_Src->colors[src[index++]];
						pic[loc++] = MakeColor(col);

						//PE_PRINT("ERROR! Unhandled code: 0x%x", code);
					}

					break;
				}
			}
		}

		unsigned int* dst = (unsigned int*)a_Dst->data;

		for (unsigned int y = 0; y < a_Dst->height; y++)
		{
			for (unsigned int x = 0; x < a_Dst->width; x++)
			{
				dst[y * pitch + x] = pic[y * pitch + x];
			}
		}

		delete pic;

		return PE_OK;
	}

	int AliceImagePmsToRaw(PEImageRaw* a_Target, AliceImagePms* a_Image)
	{
		/*a_Target->width = a_Image->width;
		a_Target->height = a_Image->height;
		a_Target->data = new pe::byte[a_Target->width * a_Target->height * 4];
		a_Target->bpp = 8;*/

		PEImageRawCreate(a_Target, a_Image->width, a_Image->height);

		if (a_Image->header->bpp == 16)
		{
			return PMSDecrypt16bit(a_Target, a_Image);
		}

		return PMSDecrypt8bit(a_Target, a_Image);
	}

	int AliceImagePmsReadAndDecrypt(PEImageRaw* a_Target, PEStream* a_Stream)
	{
		AliceImagePmsHeader header;
		if (AliceImagePmsReadHeader(&header, a_Stream) != PE_OK) 
		{ 
			AliceImagePmsDeleteHeader(&header);
			PEStreamClose(a_Stream);
			return -1; 
		}

		AliceImagePms image;
		if (AliceImagePmsReadData(&image, &header, a_Stream) != PE_OK) 
		{ 
			AliceImagePmsDeleteHeader(&header);
			AliceImagePmsDeleteData(&image);
			PEStreamClose(a_Stream);
			return -1; 
		}

		if (AliceImagePmsToRaw(a_Target, &image) != PE_OK) 
		{ 
			AliceImagePmsDeleteHeader(&header);
			AliceImagePmsDeleteData(&image);
			PEStreamClose(a_Stream);
			return -1; 
		}

		AliceImagePmsDeleteHeader(&header);
		AliceImagePmsDeleteData(&image);
		PEStreamClose(a_Stream);

		return PE_OK;
	}

	PE_EXPORT int AliceImagePmsDeleteHeader(AliceImagePmsHeader* a_Header)
	{
		return PE_OK;
	}

	PE_EXPORT int AliceImagePmsDeleteData(AliceImagePms* a_Image)
	{
		delete a_Image->header;

		delete a_Image->data;
		
		return PE_OK;
	}

	ImageAliceSoftPms::ImageAliceSoftPms() : PEImage()
	{

	}

	ImageAliceSoftPms::~ImageAliceSoftPms()
	{

	}

	int ImageAliceSoftPms::LoadHeader(PEStream* a_Stream)
	{
		m_Stream = a_Stream;
		if (AliceImagePmsReadHeader(&m_Header, a_Stream) == PE_OK)
		{
			return PE_OK;
		}

		return -1;
	}

	int ImageAliceSoftPms::LoadBody()
	{
		AliceImagePms pms_data;
		AliceImagePmsReadData(&pms_data, &m_Header, m_Stream);

		m_Data = new PEImageRaw;
		AliceImagePmsToRaw(m_Data, &pms_data);

		AliceImagePmsDeleteData(&pms_data);
		AliceImagePmsDeleteHeader(&m_Header);

		return PE_OK;
	}

};